# 前言
Parcels 是 single-spa 的一个高级特性。在对 single-spa 的注册相关api有更多了解之前,请尽量避免使用该特性。一个 single-spa 的 parcel,指的是一个与框架无关的组件,由一系列功能构成,可以被应用手动挂载,无需担心由哪种框架实现。Parcels 和注册应用的api一致,不同之处在于parcel组件需要手动挂载,而不是通过activity方法被激活。
一个 parcel 可以大到一个应用,也可以小至一个组件,可以用任何语言实现,只要能导出正确的生命周期事件即可。在 single-spa 应用中,你的SPA可能会包括很多个注册应用,也可以包含很多 parcel。通常情况下我们建议你在挂载 parcel 时传入应用的上下文,因为parcel可能会和应用一起卸载。
如果你只使用了一种框架,建议使用框架组件(如React、Vue、Angular组件)而不是 parcel 共享功能。Parcel 多包裹了一层中间层,而框架组件在应用间调用时会更容易,你可以通过 import 语法直接在注册应用里导入一个组件。只有在涉及到跨框架的应用之间进行组件调用时,我们才需要考虑 parcel 的使用
// 快速示例
// parcel 的实现
const parcelConfig = {
bootstrap() {
// 初始化
return Promise.resolve()
},
mount() {
// 使用某个框架来创建和初始化dom
return Promise.resolve()
},
unmount() {
// 使用某个框架卸载dom,做其他的清理工作
return Promise.resolve()
}
}
// 如何挂载parcel
const domElement = document.getElementById('place-in-dom-to-mount-parcel')
const parcelProps = {domElement, customProp1: 'foo'}
const parcel = singleSpa.mountRootParcel(parcelConfig, parcelProps)
// parcel 被挂载,在mountPromise中结束挂载
parcel.mountPromise.then(() => {
console.log('finished mounting parcel!')
// 如果我们想重新渲染parcel,可以调用update生命周期方法,其返回值是一个 promise
parcelProps.customProp1 = 'bar'
return parcel.update(parcelProps)
})
.then(() => {
// 在此处调用unmount生命周期方法来卸载parcel. 返回promise
return parcel.unmount()
})
# Parcel 配置
一个 parcel 只是一个由3到4个方法组成的对象。当挂载一个 parcel 时,你可以直接提供挂载对象,也可以提供 loading 方法来异步下载 parcel 对象。 parcel 对象上的每个方法都是一个生命周期函数,返回值是 promise 。Parcels 有3个必填生命周期函数(bootstrap, mount 和 unmount)和1个可选生命周期函数(update)。 强烈建议通过官方推荐的生命周期库 (opens new window)来当实现一个 parcel
一个React parcel示例如下:
// myParcel.js
import React from 'react'
import ReactDom from 'react-dom'
import singleSpaReact from 'single-spa-react'
import MyParcelComponent from './my-parcel-component.component.js'
export const MyParcel = singleSpaReact({
React,
ReactDom,
rootComponent: MyParcelComponent
})
// 在这个示例中,singleSpaReact 处理input并生成了一个含有生命周期函数的parcel
需要使用上面例子生成的 parcel,你只需引用由single-spa-react (opens new window)提供的 Parcel 组件
// mycomponent.js
import Parcel from 'single-spa-react/parcel'
import { MyParcel } from './myparcel.js'
export class myComponent extends React.Component {
render () {
return (
<Parcel
config={MyParcel}
{ /* optional props */ }
{ /* and any extra props you want here */ }
/>
)
}
}
# Parcel 生命周期
# 初始化(Bootstrap)
这个生命周期函数只在 parcel 第一次挂载前调用一次
function bootstrap(props) {
return Promise
.resolve()
.then(() => {
// 在这里做初始化相关工作
console.log('bootstrapped!')
});
}
# 挂载(Mount)
在 mountParcel 方法被调用且 parcel 未挂载时触发,一般会创建DOM元素、初始化事件监听等,从而为用户提供展示内容
function mount(props) {
return Promise
.resolve()
.then(() => {
// 在这里通知框架(如React等)渲染DOM
console.log('mounted!')
});
}
# 卸载(Unmount)
这个生命周期函数被调用的时机是parcel已经被挂载,且满足下列某个条件:
unmount()被调用- 父
parcel或者应用被卸载
当被调用时,这个方法会清除DOM元素、DOM事件监听,清理内存泄漏、全局变量、事件订阅等在挂载 parcel 时创建的内容。
function unmount(props) {
return Promise
.resolve()
.then(() => {
// 在这里通过框架语言停止渲染和移除dom
console.log('unmounted!');
});
}
# 更新(Update)(可选)
function update(props) {
return Promise
.resolve()
.then(() => {
// 在这里通过框架更新 DOM
console.log('update!');
});
}
当调用 parcel.update() 会触发更新生命周期函数。该生命周期函数是可选的,parcel 使用者需要在调用该方法之前确认其已经实现。
# 使用示例
# 模态框
App1 处理和联系人相关的所有逻辑(高内聚),但 App2 中需要新建一个联系人。 我们有以下方法在 应用1 和 应用2 中共享功能:
- 如果两个应用使用同一个框架,可以 export/import组件实现
- 重新实现一份创建联系人的逻辑(逻辑分散,不再内聚)
- 使用single-spa parcels
从 App1 导出一个 parcel,包括创建联系人的功能。这样就可以在不丢失应用高内聚特性的基础上,在跨框架的应用间共享组件行为。 App1 可以将 modal 导出作为 parcel,App2 导入该 parcel 并使用。在下面的例子中,一个主要的好处在于从 App1 导出的 parcel/modal 也将会被卸载,而无需卸载/加载 App1
// App1
export const AddContactParcel = {
bootstrap: bootstrapFn,
mount: mountFn,
unmount: unmountFn,
}
// App2
// 获取parcel,该例子使用systemJS和React
componentDidMount() {
SystemJS.import('App1').then(App1 => {
const domElement = document.body
App2MountProps.mountParcel(App1.AddContactParcel, {domElement})
})
}
# mountRootParcel 和 mountParcel
single-spa 对外暴露了 两套parcels 相关接口。二者的区别主要在于调用者和调用接口的方式
| mountRootParcel | mountParcel | |
|---|---|---|
| 上下文 | singleSpa | application |
| 卸载条件 | 手动卸载 | 手动卸载 + 应用被卸载时 |
| api 位置 | singleSpa 命名导出 | 生命周期属性中提供 |
# 我应该使用哪个
通常我们建议使用 mountParcelAPI。mountParcel 允许你将 parcel 在应用里当做一个普通组件处理,不需要考虑 parcel 由哪个框架实现,也不需要强制调用 unmount() 方法卸载 parcel
# 如何获取mountParcel API ?
为了能够绑定在应用的上下文中,mountParcel 会作为生命周期属性进行传入。你需要在自己的应用中存储和管理其方法。
mountParcel API例子:
// App1
let mountParcel
export const bootstrap = [
(props) => {
mountParcel = props.mountParcel
return Promise.resolve()
},
// 其他更多boostrap
]
注意:一些类库(如React)支持在框架里存储和管理parcel。在这些情况下我们不需要写helper方法来存储和管理mountParcel方法。